import 'package:flutter/material.dart';

class PathMetricsDemo extends StatefulWidget {
  @override
  _PathMetricsDemoState createState() => _PathMetricsDemoState();
}

class _PathMetricsDemoState extends State<PathMetricsDemo>
    with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;

  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 4), vsync: this);
    animation = Tween<double>(begin: 0, end: 1.0).animate(CurvedAnimation(
      parent: controller,
      curve: Curves.easeInOut,
    ))
      ..addListener(() {
        setState(() {});
      });
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
        child: CustomPaint(
          painter: PathMetricsPainter(
            animationValue: animation.value,
          ),
        ),
        onTap: () {
          controller.repeat();
          // if (controller.status == AnimationStatus.completed) {
          //   controller.reverse();
          // } else {
          //   controller.forward();
          // }
        });
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}

class PathMetricsPainter extends CustomPainter {
  final double animationValue;
  PathMetricsPainter({required this.animationValue});

  @override
  void paint(Canvas canvas, Size size) {
    var paint = Paint()
      ..style = PaintingStyle.stroke
      ..color = Colors.blue[400]!
      ..strokeWidth = 2.0;

    Path path = Path();
    final curveHeight = 60.0;
    final stepWidth = size.width / 4;
    path.moveTo(0, size.height / 2);
    path.quadraticBezierTo(size.width / 2 - stepWidth,
        size.height / 2 - curveHeight, size.width / 2, size.height / 2);
    path.quadraticBezierTo(size.width / 2 + stepWidth,
        size.height / 2 + curveHeight, size.width, size.height / 2);
    canvas.drawPath(path, paint);

    var metrics = path.computeMetrics();
    paint.color = Colors.red;

    for (var pathMetric in metrics) {
      var tangent =
          pathMetric.getTangentForOffset(pathMetric.length * animationValue);

      var subPath =
          pathMetric.extractPath(0.0, pathMetric.length * animationValue);
      canvas.drawPath(subPath, paint);

      paint.style = PaintingStyle.fill;
      canvas.drawCircle(tangent!.position, 4.0, paint);

      var fillPath = Path();
      fillPath.moveTo(0, size.height);
      fillPath.lineTo(0, size.height / 2);
      fillPath.addPath(subPath, Offset(0, 0));
      fillPath.lineTo(tangent.position.dx, size.height);
      fillPath.lineTo(0, size.height);
      paint.shader = LinearGradient(
        begin: Alignment.topCenter,
        end: Alignment.bottomCenter,
        colors: [Colors.red[400]!, Colors.blue[50]!],
      ).createShader(Rect.fromLTRB(
        0,
        size.height / 2 - curveHeight,
        size.width,
        size.height,
      ));
      canvas.drawPath(fillPath, paint);
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}
